package site.luojie.framework.servlet;

import org.apache.commons.lang3.StringUtils;
import org.dom4j.DocumentException;
import site.luojie.framework.annotations.*;
import site.luojie.framework.paser.XMLConfigBuilder;
import site.luojie.framework.pojo.Handler;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static site.luojie.framework.util.StringUtil.toLowerCaseFirstChar;

/**
 * @Description: 自定义前置处理器
 * @Author jie.luo
 * @Create: 2020-05-16 14:12
 **/
public class CustomDispatcherServlet extends HttpServlet {

    /**
     * 扫描的 Class 的权限定类名
     */
    private List<String> scanClassNames = new ArrayList<>();
    /**
     * ioc容器
     */
    private Map<String, Object> ioc = new HashMap<>();
    /**
     * URL与具体对应的Handler关系映射
     */
    private List<Handler> handlerMapping = new ArrayList<>();

    @Override
    public void init(ServletConfig config) throws ServletException {

        // 1.解析配置文件
        String scanPackage = doLoadConfig(config);

        // 2.根据配置文件扫描包,扫描注解
        doScan(scanPackage);

        // 3.IOC容器实例化对象
        doInstance();

        // 4.IOC容器处理依赖
        doDependent();

        // 5.初始化HandlerMapping，URL与具体对应的Handler关系映射
        initHandlerMapping();

        System.out.println("-----------------custom mvc 初始化完成---------------");
    }

    /**
     * 初始化映射器处理器
     */
    private void initHandlerMapping() {
        if (0 == ioc.size()) {
            return;
        }
        try {
            for (Map.Entry<String, Object> iocEntry : ioc.entrySet()) {

                Class<?> aClass = iocEntry.getValue().getClass();
                if (!aClass.isAnnotationPresent(CustomController.class)) {
                    continue;
                }

                String baseUrl = "";
                // 检查类上是否有 @CustomRequestMapping
                if (aClass.isAnnotationPresent(CustomRequestMapping.class)) {
                    CustomRequestMapping classCustomRequestMapping = aClass.getAnnotation(CustomRequestMapping.class);
                    baseUrl = classCustomRequestMapping.value();
                }

                String[] controllerSecurityUsers = null;
                // 检查类上是否有 @Security
                if (aClass.isAnnotationPresent(Security.class)) {
                    Security security = aClass.getAnnotation(Security.class);
                    controllerSecurityUsers = security.value();
                }

                // 获取类下所有方法
                Method[] methods = aClass.getMethods();

                for (Method method : methods) {
                    // 检查方法上是否有 @CustomRequestMapping
                    if (!method.isAnnotationPresent(CustomRequestMapping.class)) {
                        continue;
                    }

                    String[] methodSecurityUsers = null;
                    // 获取方法上的 @Security 的有访问权限用户
                    if (method.isAnnotationPresent(Security.class)) {
                        Security security = method.getAnnotation(Security.class);
                        methodSecurityUsers = security.value();
                    }

                    // 获取方法上的URL
                    CustomRequestMapping methodCustomRequestMapping = method.getAnnotation(CustomRequestMapping.class);
                    String methodUrl = methodCustomRequestMapping.value();

                    // 实例化Handler
                    Handler handler = new Handler(iocEntry.getValue(), method, Pattern.compile(baseUrl + methodUrl));

                    // 添加有权限访问的用户
                    if (null != controllerSecurityUsers) {
                        handler.getSecurityUsers().addAll(Arrays.asList(controllerSecurityUsers));
                    }
                    if (null != methodSecurityUsers) {
                        handler.getSecurityUsers().addAll(Arrays.asList(methodSecurityUsers));
                    }

                    // 遍历方法上的所有参数
                    for (int i = 0; i < method.getParameters().length; i++) {
                        Parameter parameter = method.getParameters()[i];

                        // 判断参数是否为 HttpServletRequest|HttpServletResponse,如果参数是这两，直接key为类名
                        if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
                            handler.getHandlerParameters().put(parameter.getType().getSimpleName(), i);
                        } else {
                            handler.getHandlerParameters().put(parameter.getName(), i);
                        }
                    }

                    handlerMapping.add(handler);
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * IOC容器处理依赖
     */
    private void doDependent() {
        if (0 == ioc.size()) {
            return;
        }
        try {
            for (Map.Entry<String, Object> iocEntry : ioc.entrySet()) {

                // 获取IOC容器中该对象的所有属性
                Field[] declaredFields = iocEntry.getValue().getClass().getDeclaredFields();

                for (Field declaredField : declaredFields) {

                    // 检查属性是否有 @CustomAutowired 注解
                    if (declaredField.isAnnotationPresent(CustomAutowired.class)) {
                        CustomAutowired annotation = declaredField.getAnnotation(CustomAutowired.class);

                        // @CustomAutowired 或者 @CustomAutowired("demoService")
                        String beanName = annotation.value();
                        Object bean = null;
                        if ("".equals(beanName)) {
                            // 未设置beanName，通过接口注入 或者 类注入
                            String simpleName = declaredField.getType().getSimpleName();
                            bean = ioc.get(toLowerCaseFirstChar(simpleName));
                        } else {
                            // 设置beanName，通过beanName注入
                            bean = ioc.get(beanName);
                        }

                        // 通过反射写入值
                        declaredField.setAccessible(true);
                        declaredField.set(iocEntry.getValue(), bean);
                    }
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * IOC容器实例化对象
     * 基于反射技术，实现对象创建和管理
     */
    private void doInstance() {

        if (scanClassNames.size() == 0) {
            return;
        }

        try {
            for (String className : scanClassNames) {

                // 基于全限定类名通过反射加载Class
                Class<?> scanClass = Class.forName(className);

                if (scanClass.isAnnotationPresent(CustomController.class)) {
                    // 控制层

                    // 控制层IOC容器中key直接为类名首字母小写
                    String simpleName = toLowerCaseFirstChar(scanClass.getSimpleName());
                    ioc.put(simpleName, scanClass.newInstance());

                } else if (scanClass.isAnnotationPresent(CustomService.class)) {
                    // 业务层

                    CustomService service = scanClass.getAnnotation(CustomService.class);
                    // @CustomService("demoService") 或者 @CustomService
                    String value = service.value();

                    // 注解设置value
                    if (!"".equals(value)) {
                        ioc.put(value, scanClass.newInstance());
                    } else {
                        // 注解未设置value
                        ioc.put(toLowerCaseFirstChar(scanClass.getSimpleName()), scanClass.newInstance());
                    }

                    // 如果类实现了接口，需要根据接口名小写在IOC容器存放一份
                    // public class DemoService implements IDemoService
                    for (Class<?> anInterface : scanClass.getInterfaces()) {
                        // iDemoService
                        ioc.put(toLowerCaseFirstChar(anInterface.getSimpleName()), scanClass.newInstance());
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 根据 scanPackage 扫描类（递归）
     * site.luojie.demo  --> site/luojie/demo
     *
     * @param scanPackage 扫描包
     */
    private void doScan(String scanPackage) {

        if (null == scanPackage) {
            return;
        }

        // 获取 scanPackage 的磁盘路径
        String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() +
                scanPackage.replaceAll("\\.", "/");

        File files = new File(scanPackagePath);
        for (File listFile : files.listFiles()) {
            // 如果为文件夹继续通过递归继续向下扫描
            if (listFile.isDirectory()) {
                doScan(scanPackage + "." + listFile.getName());
            } else {
                String className = scanPackage + "." + listFile.getName().replaceAll(".class", "");
                scanClassNames.add(className);
            }
        }
    }

    /**
     * 解析配置文件
     *
     * @param config ServletConfig
     * @return 扫描的包路径
     */
    private String doLoadConfig(ServletConfig config) {
        // 获取容器初始化配置的配置文件
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        // 加载配置文件
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        // 解析配置文件，获取需要扫描的package
        try {
            return new XMLConfigBuilder().parse(resourceAsStream);
        } catch (DocumentException e) {
            System.out.println("--------------配置文件解析失败------------");
            e.printStackTrace();
            return null;
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        Handler handler = getHandler(request);
        if (null == handler) {
            response.getWriter().write("404 resource not found");
            return;
        }

        // 判断当前访问用户是否又权限访问
        if (handler.getSecurityUsers().size() > 0) {
            String username = request.getParameter("username");
            if (null == username || "".equals(username.trim()) || !handler.getSecurityUsers().contains(username)) {
                response.getWriter().write("401 unauthorized");
                return;
            }
        }

        // Handler的所有参数类型
        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
        // 反射调用的参数信息
        Object[] parameters = new Object[parameterTypes.length];
        // request 请求中的参数集合
        Map<String, String[]> parameterMap = request.getParameterMap();

        // 遍历 request 中的参数,将参数放进对应handler中方法参数中
        for (Map.Entry<String, String[]> stringEntry : parameterMap.entrySet()) {
            // name=1&name=2
            String paramValue = StringUtils.join(stringEntry.getValue());

            // 判断方法上的形参是否能够和前端传递的参数匹配，如果匹配则设置
            if (handler.getHandlerParameters().containsKey(stringEntry.getKey())) {
                // 获取出参数对应方法上的索引位置
                Integer index = handler.getHandlerParameters().get(stringEntry.getKey());
                // 参数放入对应的索引位置
                parameters[index] = paramValue;
            }
        }

        // 检测 Handler 中参数是否有 HttpServletRequest，有则放入
        if (handler.getHandlerParameters().containsKey(HttpServletRequest.class.getSimpleName())) {
            Integer index = handler.getHandlerParameters().get(HttpServletRequest.class.getSimpleName());
            parameters[index] = request;
        }

        // 检测 Handler 中参数是否有 HttpServletResponse，有则放入
        if (handler.getHandlerParameters().containsKey(HttpServletResponse.class.getSimpleName())) {
            Integer index = handler.getHandlerParameters().get(HttpServletResponse.class.getSimpleName());
            parameters[index] = response;
        }

        try {
            Object invoke = handler.getMethod().invoke(handler.getController(), parameters);
            response.getWriter().write((String) invoke);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据 URI 获取 Handler
     *
     * @param request HttpServletRequest
     * @return Handler
     */
    private Handler getHandler(HttpServletRequest request) {

        if (handlerMapping.isEmpty()) {
            return null;
        }

        // 请求地址
        String requestURI = request.getRequestURI();

        for (Handler handler : handlerMapping) {
            // 根据正则进行匹配地址是否存在映射
            Matcher matcher = handler.getPattern().matcher(requestURI);
            if (matcher.matches()) {
                return handler;
            }
        }

        return null;
    }

    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

}
